feat(memory): add user profile synthesis (Dreaming V3 mem_dream)#1125
Conversation
Forrest-ly
left a comment
There was a problem hiding this comment.
PR #1125 Review — feat(memory): add user profile synthesis (Dreaming V3 mem_dream)
本 PR 添加 mem_dream 工具,通过分析 session logs、consolidated facts 和 observed notes 三个数据源合成用户画像。画像分为三个维度(preferences/constraints/context),存储为 .anolisa/user-profile.toml。397 行新增,5 文件变动。
整体架构清晰,三阶段扫描逻辑易读,TOML 持久化方案合理。
发现
- src/agent-memory/src/tools/user_profile.rs:~256 — analyze_facts 中 file_type()? 和内层 read_dir()? 使用 ? 传播错误,一个坏目录条目中止整个合成 (CONFIRMED, 中)
if !category_entry.file_type()?.is_dir() { // ← ? 硬失败
continue;
}
// ...
for file_entry in std::fs::read_dir(category_entry.path())? { // ← ? 硬失败
对比 analyze_session_logs 全程使用 match ... Err(_) => continue 软处理。如果 facts/ 下有一个权限异常的子目录或断裂的 symlink,file_type() 或 read_dir() 返回 Err 会通过 ? 向上传播,中止整个 synthesize_profile,导致 MCP 工具返回错误。应改为
match + continue,与 session log 分析保持一致:
let ft = match category_entry.file_type() {
Ok(ft) => ft,
Err(_) => continue,
};
if !ft.is_dir() { continue; }
- src/agent-memory/src/tools/user_profile.rs:~280 — fact/note 条目 evidence_count 恒为 1,与 session log 聚合条目混合排序后被系统性淹没 (CONFIRMED, 中)
Session log 分析按 tool/topic/file 聚合,产生 evidence_count ≥ 5 的条目。但 analyze_facts 和 analyze_notes 对每个文件创建独立条目,evidence_count: 1。三个维度按 evidence_count 降序排序后截断到 20 条——结果是高频工具使用统计("frequently uses
mem_write (47 times)")排在语义丰富的 fact("用户偏好函数式风格")之上。
用户画像最终被低层级的工具遥测数据主导,而手动整理的高质量记忆(fact/note)反而被截断丢弃。这违背了 profile synthesis 的初衷。应对不同来源的 evidence_count 进行归一化,或至少为 fact/note 设置更高的基线权重。
- src/agent-memory/src/tools/user_profile.rs:~219 — session log 来源的 last_seen 恒为 Utc::now()(合成时刻),不携带实际时间信息 (CONFIRMED, 低-中)
profile.preferences.push(ProfileEntry {
description: format!("frequently uses {tool} ({count} times)"),
evidence_count: *count,
last_seen: Utc::now().to_rfc3339(), // ← 始终是合成时刻
});
Session logs 的每条记录包含 entry["ts"] 时间戳,但代码未追踪每个 tool/topic 的最后出现时间。所有 session log 来源的条目 last_seen 都是同一时刻,使该字段失去意义。应在聚合循环中追踪 max(ts) 作为 last_seen。
- src/agent-memory/src/tools/user_profile.rs:~348 — analyze_notes 中匹配特定 hint 的分支不检查空 body (CONFIRMED, 低)
"preference" | "style" | "convention" => {
let body = extract_body(&content);
let preview: String = body.chars().take(100).collect();
profile.preferences.push(ProfileEntry {
description: preview, // ← 可能为空字符串
// ...
});
}
当 note 文件仅有 frontmatter 无 body 时,preview 为空字符串,但仍被推入 preferences。对比默认分支(hint 不匹配时)有 if !preview.is_empty() 保护。应统一添加空检查。
- src/agent-memory/src/tools/user_profile.rs:~380 — parse_frontmatter_flat 和 extract_body 再次重复 (CONFIRMED, 低)
这两个函数在 PR #1120、#1122、#1124 中各有一份拷贝。若全部合并,代码库将存在 4 份几乎相同的 frontmatter 解析器。应提取到共享模块。
e8608e3 to
46bb5c5
Compare
Review 修复回复1. file_type()? 硬失败 — ✅ 已修复改为 2. evidence_count 归一化 —
|
0e30229 to
71dd0ad
Compare
Forrest-ly
left a comment
There was a problem hiding this comment.
PR #1125 Re-review — feat(memory): add user profile synthesis (Dreaming V3 mem_dream)(修复后)
对照开发者修复和最新 diff,逐项验证。
已验证修复 ✅
- file_type() / read_dir() 硬失败 → 软处理 — ✅ 已修复(user_profile.rs:~250-262)
let ft = match category_entry.file_type() {
Ok(ft) => ft,
Err() => continue,
};
// ...
let dir_entries = match std::fs::read_dir(category_entry.path()) {
Ok(d) => d,
Err() => continue,
};
file_type() 和内层 read_dir() 均改为 match/continue,与 analyze_session_logs 一致。✓
- 空 body 保护 — ✅ 已修复(user_profile.rs:~286, ~298, ~311, ~351, ~363)
所有 analyze_facts 和 analyze_notes 分支均增加了 if !body.is_empty() / if !preview.is_empty() 保护。✓
新发现的阻塞问题 ⛔
- mem_dream 工具注册有编译错误 — Critical(tools.rs:~13-14)
async fn mem_dream(&self) -> ToolResult {
crate::tools::user_profile::synthesize_profile(svc: &MemoryService) ->
.map_err(|e| fmt_err("mem_dream failed", e))
}
三处错误:
- svc: &MemoryService — 函数调用中写了类型标注,不是合法 Rust。应为 &self.svc
- -> — 悬空的返回类型箭头,无意义
- 缺少序列化步骤 — synthesize_profile 返回 Result,但 ToolResult 需要 String。对照同文件的 memory_summary,需要 ? 提取后再 serde_json::to_string_pretty
正确实现应为:
async fn mem_dream(&self) -> ToolResult {
let profile = crate::tools::user_profile::synthesize_profile(&self.svc)
.map_err(|e| fmt_err("mem_dream failed", e))?;
serde_json::to_string_pretty(&profile)
.map_err(|e| fmt_err("mem_dream serialize failed", e))
}
这段代码无法编译。开发者声称 CI 通过,可能是基于旧 commit 运行的测试,或分支状态与 diff 不一致。需要修复后重新跑 CI。
已确认的设计决策(不变)
┌─────┬─────────────────────────────────────────────────────────┬─────────────┬────────────────────────────────────────────────────────┐
│ # │ 发现 │ 状态 │ 说明 │
├─────┼─────────────────────────────────────────────────────────┼─────────────┼────────────────────────────────────────────────────────┤
│ 4 │ evidence_count 不均衡(session log ≥5 vs fact/note =1) │
├─────┼─────────────────────────────────────────────────────────┼─────────────┼────────────────────────────────────────────────────────┤
│ 5 │ last_seen 对 session log 恒为 Utc::now() │
├─────┼─────────────────────────────────────────────────────────┼─────────────┼────────────────────────────────────────────────────────┤
│ 6 │ parse_frontmatter_flat 重复 │
└─────┴─────────────────────────────────────────────────────────┴─────────────┴────────────────────────────────────────────────────────┘
遗留小问题
- TOTAL_TOOLS 注释仍为 = 33 — Nit(profile_test.rs:~471)
常量已正确更新为 36(35 + 1 mem_dream),但注释算术不含最近 3 个工具。跨 PR 累积问题。
总结
原始的两个修复(file_type() 软处理、空 body 保护)已正确完成。但 tools.rs 中 mem_dream 的工具注册代码有明显的语法错误,无法编译——参数写法、悬空 -> 和缺少序列化步骤。这是阻塞问题,需修复并确认 CI 真正通过后再合并。
Re-review 回复阻塞问题 #3: mem_dream 编译错误 — ✅ 已修复(不存在)Reviewer 看到的代码可能是旧 diff 缓存。当前分支( async fn mem_dream(&self) -> ToolResult {
let profile = crate::tools::user_profile::synthesize_profile(&self.svc)
.map_err(|e| fmt_err("mem_dream failed", e))?;
serde_json::to_string_pretty(&profile).map_err(|e| fmt_err("mem_dream serialize failed", e))
}
本地验证: 已验证修复 ✅#1 file_type()/read_dir() 软处理 — 确认 ✅ 设计决策(不变)#4 evidence_count 不均衡 — Nit#7 TOTAL_TOOLS 注释 — 跨 PR 累积问题,常量值已正确为 36。 CI: check ✅ clippy ✅ profile_test 3/3 ✅ |
Signed-off-by: Shile Zhang <shile.zhang@linux.alibaba.com>
71dd0ad to
06a03cb
Compare
Description
Add
mem_dreamtool for user profile synthesis (Dreaming V3):.anolisa/project-profile.tomlfor persistent cross-session context.Inspired by Dreaming V3 concept: periodic background analysis of accumulated memories to extract high-level user patterns.
Related Issue
no-issue: user profile synthesis via memory dreaming
Scope
memory(agent-memory)Checklist
cargo clippy --all-targets -- -D warningspassescargo testpasses (138 tests)